Whisper Isle 遊戲連結(PC Only):whisperisle.app(註:專案每日持續高速演進,撰文當下線上已推進至 v0.2.4)
昨天在 Day 10 聊了島嶼從 80×80 蓋到 180×180 的三次重建,以及地圖尺寸問題這件事。地基定了,下一個問題馬上浮出來:這 180×180 格的地面,要用什麼畫出來?開工以來我們一直走的是傳統做法:32×32 的 tile 拼地面,再用生圖工具逐件生成岩壁、樹叢、木箱這類 prop 散佈上去。到了 8 月初,島上已經擺了 609 件 prop,光岩壁素材就有 56 件。
但從玩家角度來看 AI 的組合,大概就是亂做一通。
我最初的懷疑方向是錯的。我懷疑的是 web 平台:是不是瀏覽器就做不出有層次感的地形?但讓 AI 查詢了主流的開發做法:
它同時查了先例:柏德之門用的是整幅預渲染背景(原生 5120×3480)加上預渲染八方向 sprite 的角色與怪物,再用資料檔標出不可走區。Pillars of Eternity 也是整幅背景,Maya 渲成品、深度、法線、顏色四層。
這一點很關鍵:柏德之門的架構,跟我們的角色管線是同構的。 從 Meshy 到 Blender 八方向 sprite,就是那條線。差別只在地面:人家先畫一整幅再切塊儲存,我們是拼零件當場組。再對照生圖工具的能力邊界:整幅構圖是它的強項(HUD 那張參照圖本身就是證據),可拼接的零件是它的弱項(56 件素材的下場)。結論很單純,把 AI 換到它強的那一側。
原本的流程是我先排 layout,標好碼頭、泊位、NPC 站位,再請生圖照座標畫。改成:
imagegen 自由生底圖
→ 我目視迭代定稿
→ 照定稿的畫,反向標定遊戲空間資料
(手塗可走遮罩/重標 spawn、NPC 站位、泊位、出入口)
→ 編譯 walkable 寫回 isle.json
→ 前景切分(樹冠、屋簷、崖壁上緣另存透明層,插進 y-sort)
架構上守住的是不變項:server 的 180×180 walkable grid、碰撞、尋路、怪物區、多人同步,零改動。server 對「畫」零感知,它只看遮罩編譯出來的格子。Gate 1 是一張合成驗證圖,零 code:把底圖候選跟現役的騎士、狼、NPC sprite 用影像工具疊成假截圖,我看角色會不會「浮」在畫上。角色來自 Meshy,底圖來自 imagegen,兩台生成器的風格要咬合。
結果是咬合過了,代價是一個暖調 tint。場景內的角色統一乘上 0xffe3bf,把 Meshy 的冷灰 sprite 拉向暖調底圖。2026-08-04 凌晨 00:08,港區整幅底圖上機(commit 756d594):一張 2x 銳化母帶 webp,1.52MB,96px 羽化邊,manifest 裡寫死世界座標 (3900, 4200) 與 3072×2048 的擺放契約,client 與地圖工具共用這一份真相。
驗證不是看圖,是跑真實 BFS:walkpoint 斷點 0,六個怪區可達率 81/81、55/72、453/479、178/180、562/586、778/779,全島總可達 10,376 格。還有一個橋咽喉性測試:把橋面禁走,東岸全域必須不可達,證明橋真的是唯一通路。
在拼 tile 的年代,畫面和碰撞來自同一份資料:tile 是什麼,可不可走就是什麼。整幅母畫上機之後,這個關係斷了。
畫是真相 A。 玩家看到的橋、深溝、岸線、崖壁,全在畫裡。
碰撞是真相 B。 它是 apply_scene_paint.py 用 classify_* 這組分類器,從畫的顏色反推出來的:綠的是草、藍的是水、灰的是岩、暗灰的是落塵坪。
兩套真相的縫,就在「反推」這兩個字。分類器只看顏色,不看語意。一條橋在畫裡是木頭色,它可能判成 DIRT 可走,也可能判成 ROCK 牆。一道深溝是暗灰色,落塵分類器一看,暗但開闊,判可走。玩家走過去,掉進畫裡的溝。
29 張場景圖用 Promise.all 並行載入,每張載完就 push 進陣列,完成序取代了 manifest 序。z 序契約「底毯在前、hero 在後」只寫在註解裡,沒落在 code。哪張底毯比港區 hero 幅晚載完,就蓋掉整個港村。單幅時代這個序沒差,24 幅入庫後它變成潛在的地雷。
修法一行:載入完成後按 manifest key 序 sort 回來。
// assetLoader.ts(commit df22d3a):完成序 ≠ z 序,載完後按 manifest 契約排回
const scenePaintOrder = new Map(scenePaintEntries.map(([key], i) => [key, i]));
result.scenePaints.sort(
(a, b) => (scenePaintOrder.get(a.key) ?? 0) - (scenePaintOrder.get(b.key) ?? 0),
);
一個獨立的 design-agent,24 幅全看,另外對 9 個鄰接熱點做像素掃描量測:岸線的海色偵測、道路的暖亮偵測,再拿 layout 參考圖仲裁偏差歸誰。被退的 10 幅,每一幅都附 pin 值,直接寫進重生成 prompt 的硬幾何約束段。舉兩張:
重點是報告裡的一句話:這些 FIX 全是視覺鬼影級,可走性已經被接橋 poly 保住,重畫是為了顏值,不阻擋遊玩。也就是說,蓋章側先用人工多邊形把路接通,畫幅慢慢重生。
還有一條重生成紀律,後來變成整個專案的素材原則:一律回乾淨基底加原始附件重生成,絕不拿上一版輸出當基底。 生圖工具拿自己的輸出再改,會把上一版的錯一起繼承。
整幅母畫這條路,畫面質感確實上了一個檔次,重複且破碎的 tile 感消失了。但「空間資料整批重標」跟「畫與碰撞不同源」是兩件事,前者是一次性成本,後者是持續。這就是 AI 產線的一個真相:它把生成的邊際成本壓到接近零,品管與一致性的成本一毛都沒少,只是從前面搬到了後面。